package ouisseuk.tools;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import ouisseuk.model.Artist;
import ouisseuk.model.Image;
import ouisseuk.model.Release;
import ouisseuk.model.ReleaseFromArtist;
import ouisseuk.model.SearchResult;
import ouisseuk.model.SearchResults;
import ouisseuk.model.Track;

import com.jcraft.jzlib.JZlib;
import com.jcraft.jzlib.ZStream;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
public class Discogs {

	private static void CHECK_ERR(ZStream z, int err, String msg) {
		if (err != JZlib.Z_OK) {
			if (z.msg != null)
				System.out.print(z.msg + " ");
			System.out.println(msg + " error: " + err);
			System.exit(1);
		}
	}

	public static byte[] getDecodedResponse(URL url) throws IOException {
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.addRequestProperty("Accept-Encoding", "gzip");
		// Object content = conn.getContent();
		String encoding = conn.getContentEncoding();
		String type = conn.getContentType();
		int length = conn.getContentLength();
		Map<String, List<String>> headerFields = conn.getHeaderFields();
		// System.out.println(content);
		System.out.println(encoding);
		System.out.println(type);
		System.out.println("length: " + length);
		System.out.println(headerFields);

		if (encoding != null && encoding.equals("gzip")) {
			// BufferedReader ir = new BufferedReader(new
			// InputStreamReader(conn.getInputStream()));
			// while(ir.ready())
			// {
			// System.out.println(ir.readLine());
			// }
			int gzipHeaderLength = 10;
			int gzipFooterLength = 8;
			int deflatedDataLength = length - gzipHeaderLength - gzipFooterLength;
			int zlibHeaderLength = 2;
			int zlibFooterLength = 1;
			int zlibStreamLength = deflatedDataLength + zlibHeaderLength + zlibFooterLength;
			byte[] zlibStream = new byte[zlibStreamLength];
			InputStream is = conn.getInputStream();
			int id1 = is.read();
			assert id1 == 31;
			int id2 = is.read();
			assert id2 == 139;
			int cm = is.read();
			assert cm == 8;

			@SuppressWarnings("unused")
			int flg = is.read();

			// System.out.println("flg: " + flg);
			int time1 = is.read();
			int time2 = is.read();
			int time3 = is.read();
			int time4 = is.read();
			int mtime = ((time4 & 0xFF) << 24) | ((time3 & 0xFF) << 16) | ((time2 & 0xFF) << 8) | (time1 & 0xFF);
			Date date = new Date((long) mtime * 1000);
			System.out.println("date: " + date);
			int xfl = is.read();
			System.out.println("xfl: " + xfl);
			int os = is.read();
			System.out.println("os: " + os);

			int cinfo = 7;
			int cmf = ((cinfo & 0xF) << 4) | (cm & 0xF);
			System.out.println("cmf: " + cmf);
			zlibStream[0] = (byte) (cmf & 0xFF);

			int flevel = 3; // 11 -> 11000000 (192)
			int flevel_6 = (flevel & 0xFF) << 6;

			// (2048 + 192 + ? ) % 31 == 0
			int cmf_8 = (cmf & 0xFF) << 8;
			int rest = (cmf_8 + flevel_6) % 31;
			int fcheck;
			if (rest == 0)
				fcheck = 0;
			else
				fcheck = 31 - rest;
			System.out.println("fcheck: " + fcheck);
			int flg2 = flevel_6 + fcheck;
			zlibStream[1] = (byte) (flg2 & 0xFF);

			int other;
			for (int k = zlibHeaderLength; k < zlibStream.length - zlibFooterLength; k++) {
				other = is.read();
				// System.err.println(other);
				zlibStream[k] = (byte) (other & 0xFF);
			}

			// for (byte b : zlibStream) {
			// int a = b & 0x000000FF;
			// System.out.println(a);
			// }

			int crc32_1 = is.read();
			int crc32_2 = is.read();
			int crc32_3 = is.read();
			int crc32_4 = is.read();
			@SuppressWarnings("unused")
			int crc32 = ((crc32_4 & 0xFF) << 24) | ((crc32_3 & 0xFF) << 16) | ((crc32_2 & 0xFF) << 8) | (crc32_1 & 0xFF);
			// System.out.println("crc32: " + crc32);

			int isize_1 = is.read();
			int isize_2 = is.read();
			int isize_3 = is.read();
			int isize_4 = is.read();
			int isize = ((isize_4 & 0xFF) << 24) | ((isize_3 & 0xFF) << 16) | ((isize_2 & 0xFF) << 8) | (isize_1 & 0xFF);
			// System.out.println("isize_1: " + isize_1);
			// System.out.println("isize_2: " + isize_2);
			// System.out.println("isize_3: " + isize_3);
			// System.out.println("isize_4: " + isize_4);
			// System.out.println("isize: " + isize);

			int uncomprLen = isize;
			byte[] uncompr = new byte[uncomprLen];

			int err = 0;

			ZStream d_stream = new ZStream();

			d_stream.next_in = zlibStream;
			d_stream.next_in_index = 0;
			d_stream.next_out = uncompr;
			d_stream.next_out_index = 0;

			err = d_stream.inflateInit();
			CHECK_ERR(d_stream, err, "inflateInit");

			while (d_stream.total_out < uncomprLen && d_stream.total_in < zlibStreamLength) {
				d_stream.avail_in = d_stream.avail_out = 1; /*
																								 * force small
																								 * buffers
																								 */
				err = d_stream.inflate(JZlib.Z_NO_FLUSH);
				if (err == JZlib.Z_STREAM_END)
					break;
				CHECK_ERR(d_stream, err, "inflate");
			}

			err = d_stream.inflateEnd();
			CHECK_ERR(d_stream, err, "inflateEnd");

			/*
			 * int d = 0; byte current = uncompr[0];
			 * System.err.print((char)current); d++; while (d < isize) { current
			 * = uncompr[d]; System.err.print((char)current); d++; }
			 * 
			 * System.out.println(); System.out.println("d: "+d);
			 */

			return uncompr;
		} else {
			return null;
		}
	}

	public static SearchResults getSearchResults(String searched) throws IOException, ParserConfigurationException, SAXException {
		// TODO: do that better
		System.out.println("searched: " + searched);
		String[] searchedWords = searched.trim().split(" ");

		String searchString = "";
		for (int i = 0; i < searchedWords.length; i++) {
			searchString += searchedWords[i];
			if (i < searchedWords.length - 1)
				searchString += "+";
		}

		URL url = new URL("http://www.discogs.com/search?type=all&q=" + searchString + "&f=xml&api_key=52a1898466");
		System.out.println("url: " + url);

		byte[] res = getDecodedResponse(url);

		SearchResults searchResults = new SearchResults();

		if (res != null) {
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse(new ByteArrayInputStream(res));
			doc.getDocumentElement().normalize();
			Node rootNode = doc.getDocumentElement();
			NodeList mainNodeList = rootNode.getChildNodes();

			for (int i = 0; i < mainNodeList.getLength(); i++) {
				Node mainNode = mainNodeList.item(i);
				NodeList searchResultList = mainNode.getChildNodes();

				if (mainNode.getNodeName().equals("exactresults")) {
					for (int j = 0; j < searchResultList.getLength(); j++) {
						SearchResult searchResult = new SearchResult();

						Node result = searchResultList.item(j);
						NodeList resultSubNodeList = result.getChildNodes();

						NamedNodeMap attributes = result.getAttributes();
						Node typeAttribute = attributes.getNamedItem("type");
						SearchResult.Type type = SearchResult.Type.valueOf(typeAttribute.getNodeValue());
						searchResult.setType(type);
						System.out.println("type: " + type.name() + " - " + type.ordinal());

						for (int k = 0; k < resultSubNodeList.getLength(); k++) {
							Node subNode = resultSubNodeList.item(k);

							String subNodeValue = subNode.getChildNodes().item(0).getNodeValue();
							// System.err.println("subNodeValue0: " +
							// subNodeValue);

							if (subNode.getNodeName().equals("title"))
								searchResult.setTitle(subNodeValue);
							else if (subNode.getNodeName().equals("uri"))
								searchResult.setUri(subNodeValue);
						}

						searchResults.addExactResult(searchResult);
					}
				} else if (mainNode.getNodeName().equals("searchresults")) {
					for (int j = 0; j < searchResultList.getLength(); j++) {
						SearchResult searchResult = new SearchResult();

						Node result = searchResultList.item(j);
						NodeList resultSubNodeList = result.getChildNodes();

						NamedNodeMap attributes = result.getAttributes();
						Node typeAttribute = attributes.getNamedItem("type");
						SearchResult.Type type = SearchResult.Type.valueOf(typeAttribute.getNodeValue());
						searchResult.setType(type);
						// System.out.println("type: "+type.name()+" - "+type.ordinal());

						for (int k = 0; k < resultSubNodeList.getLength(); k++) {
							Node subNode = resultSubNodeList.item(k);

							String subNodeValue = subNode.getChildNodes().item(0).getNodeValue();
							// System.err.println("subNodeValue1: " +
							// subNodeValue);

							if (subNode.getNodeName().equals("title"))
								searchResult.setTitle(subNodeValue);
							else if (subNode.getNodeName().equals("uri"))
								searchResult.setUri(subNodeValue);
							else if (subNode.getNodeName().equals("summary"))
								searchResult.setSummary(subNodeValue);
						}

						searchResults.addOtherResult(searchResult);
					}
				}
			}
		}

		/*
		 * NodeList nodeList = doc.getElementsByTagName("images"); Node
		 * imagesNode = nodeList.item(0); Element imagesElement = (Element)
		 * imagesNode; NodeList imageNodeList = imagesNode.getChildNodes(); Node
		 * imageNode = imageNodeList.item(0);
		 */

		return searchResults;
	}

	public static Release getRelease(String uri) throws IOException, ParserConfigurationException, SAXException {
		int lastSlashIndex = uri.lastIndexOf("/");
		String rid = uri.substring(lastSlashIndex + 1, uri.length());

		return getReleaseFromRid(rid);
	}

	public static Release getReleaseFromRid(String rid) throws IOException, ParserConfigurationException, SAXException {
		String urlString = "http://www.discogs.com/release/" + rid + "?f=xml&api_key=52a1898466";
		URL url = new URL(urlString);

		byte[] res = getDecodedResponse(url);

		Release release = new Release();

		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.parse(new ByteArrayInputStream(res));
		doc.getDocumentElement().normalize();
		Node rootNode = doc.getDocumentElement();
		NodeList mainNodeList = rootNode.getChildNodes();
		Node releaseNode = mainNodeList.item(0);
		NodeList releaseNodeList = releaseNode.getChildNodes();

		for (int i = 0; i < releaseNodeList.getLength(); i++) {

			Node subNode = releaseNodeList.item(i);
			// System.out.println("subNode2: "+subNode);

			String subNodeValue = subNode.getChildNodes().item(0).getNodeValue();
			System.out.println(subNodeValue);

			if (subNode.getNodeName().equals("title"))
				release.setTitle(subNodeValue);
			else if (subNode.getNodeName().equals("country"))
				release.setCountry(subNodeValue);
			else if (subNode.getNodeName().equals("released"))
				release.setReleased(subNodeValue);
			else if (subNode.getNodeName().equals("artists")) {
				NodeList artists = subNode.getChildNodes();
				for (int j = 0; j < artists.getLength(); j++) {
					Node artist = artists.item(j);
					Node artistName = artist.getChildNodes().item(0);
					String artistValue = artistName.getChildNodes().item(0).getNodeValue();
					release.addArtist(artistValue);
					System.out.println("artistValue: " + artistValue);
				}
			} else if (subNode.getNodeName().equals("labels")) {
				NodeList labels = subNode.getChildNodes();
				for (int j = 0; j < labels.getLength(); j++) {
					Node label = labels.item(j);
					NamedNodeMap labelAttributes = label.getAttributes();
					Node labelName = labelAttributes.getNamedItem("name");
					String labelNameValue = labelName.getNodeValue();
					release.addLabel(labelNameValue);
					System.out.println("labelNameValue: " + labelNameValue);
				}
			} else if (subNode.getNodeName().equals("images")) {
				NodeList images = subNode.getChildNodes();
				for (int j = 0; j < images.getLength(); j++) {
					Node imageNode = images.item(j);
					NamedNodeMap imageAttributes = imageNode.getAttributes();

					Node uriNode = imageAttributes.getNamedItem("uri");
					String uri = uriNode.getNodeValue();
					System.out.println("uri: " + uri);

					Node smallUriNode = imageAttributes.getNamedItem("uri150");
					String smallUri = smallUriNode.getNodeValue();
					System.out.println("smallUri: " + smallUri);

					Node widthNode = imageAttributes.getNamedItem("width");
					String width = widthNode.getNodeValue();
					System.out.println("width: " + width);

					Node heightNode = imageAttributes.getNamedItem("height");
					String height = heightNode.getNodeValue();
					System.out.println("height: " + height);

					Image image = new Image();
					image.setUri(uri);
					image.setSmallUri(smallUri);
					release.addImage(image);
				}
			} else if (subNode.getNodeName().equals("tracklist")) {
				NodeList tracks = subNode.getChildNodes();
				for (int j = 0; j < tracks.getLength(); j++) {
					Track track = new Track();
					Node trackNode = tracks.item(j);
					NodeList trackSubNodes = trackNode.getChildNodes();
					for (int k = 0; k < trackSubNodes.getLength(); k++) {
						Node trackSubNode = trackSubNodes.item(k);
						String trackSubNodeValue = null;
						if (trackSubNode.getChildNodes().getLength() != 0)
							trackSubNodeValue = trackSubNode.getChildNodes().item(0).getNodeValue();

						if (trackSubNodeValue != null) {
							System.out.println("trackSubNodeValue: " + trackSubNodeValue);
							if (trackSubNode.getNodeName().equals("position")) {
								track.setPosition(trackSubNodeValue);
							} else if (trackSubNode.getNodeName().equals("title")) {
								track.setTitle(trackSubNodeValue);
							} else if (trackSubNode.getNodeName().equals("duration")) {
								track.setDuration(trackSubNodeValue);
							}
						}
					}
					release.addTrack(track);
				}
			}

		}

		return release;
	}

	public static Artist getArtist(SearchResult searchResult) throws IOException, ParserConfigurationException, SAXException {
		String uri = searchResult.getUri();

		String urlString = uri + "?f=xml&api_key=52a1898466";
		URL url = new URL(urlString);

		byte[] res = getDecodedResponse(url);

		Artist artist = new Artist();

		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.parse(new ByteArrayInputStream(res));
		doc.getDocumentElement().normalize();
		Node rootNode = doc.getDocumentElement();
		NodeList mainNodeList = rootNode.getChildNodes();
		Node artistNode = mainNodeList.item(0);
		NodeList artistNodeList = artistNode.getChildNodes();

		for (int i = 0; i < artistNodeList.getLength(); i++) {

			Node subNode = artistNodeList.item(i);
			// System.out.println("subNode3: "+subNode);

			String subNodeValue = subNode.getChildNodes().item(0).getNodeValue();
			// System.out.println(subNodeValue);

			if (subNode.getNodeName().equals("name"))
				artist.setName(subNodeValue);
			else if (subNode.getNodeName().equals("releases")) {
				NodeList releases = subNode.getChildNodes();
				for (int j = 0; j < releases.getLength(); j++) {
					Node releaseNode = releases.item(j);
					NodeList releaseSubNodes = releaseNode.getChildNodes();

					NamedNodeMap releaseAttributes = releaseNode.getAttributes();
					Node ridNode = releaseAttributes.getNamedItem("id");
					String rid = ridNode.getNodeValue();
					// System.out.println("rid: "+rid);
					ReleaseFromArtist release = new ReleaseFromArtist(rid);

					for (int k = 0; k < releaseSubNodes.getLength(); k++) {
						Node releaseSubNode = releaseSubNodes.item(k);
						String releaseSubNodeValue = releaseSubNode.getChildNodes().item(0).getNodeValue();
						// System.out.println("releaseSubNodeValue: "+releaseSubNodeValue);

						if (releaseSubNode.getNodeName().equals("title"))
							release.setTitle(releaseSubNodeValue);
						else if (releaseSubNode.getNodeName().equals("format"))
							release.setFormat(releaseSubNodeValue);
						else if (releaseSubNode.getNodeName().equals("label"))
							release.setLabel(releaseSubNodeValue);
						else if (releaseSubNode.getNodeName().equals("year"))
							release.setYear(releaseSubNodeValue);
					}

					artist.addRelease(release);
				}
			} else if (subNode.getNodeName().equals("images")) {
				NodeList images = subNode.getChildNodes();
				for (int j = 0; j < images.getLength(); j++) {
					Node imageNode = images.item(j);
					NamedNodeMap imageAttributes = imageNode.getAttributes();

					Node uriNode = imageAttributes.getNamedItem("uri");
					String bigUri = uriNode.getNodeValue();
					System.out.println("bigUri: " + bigUri);

					Node smallUriNode = imageAttributes.getNamedItem("uri150");
					String smallUri = smallUriNode.getNodeValue();
					System.out.println("smallUri: " + smallUri);

					Node widthNode = imageAttributes.getNamedItem("width");
					String width = widthNode.getNodeValue();
					System.out.println("width: " + width);

					Node heightNode = imageAttributes.getNamedItem("height");
					String height = heightNode.getNodeValue();
					System.out.println("height: " + height);

					Image image = new Image();
					image.setUri(uri);
					image.setSmallUri(smallUri);
					artist.addImage(image);
				}
			}

		}

		return artist;
	}

}
